#include "Light.h"
#include "../OpenGL.h"
#include "Shapes3D/Point3D.h"
#include "GameController.h"
#include "Logger.h"
#include <assert.h>

#define DEFAULT_INTENSITY_AMBIENT  0.2f
#define DEFAULT_INTENSITY_DIFFUSE  1.0f - DEFAULT_INTENSITY_AMBIENT
#define DEFAULT_INTENSITY_SPECULAR 1.0f

#define DEFAULT_CONSTANT_ATTENUATION 1.5
#define DEFAULT_LINEAR_ATTENUATION 0.5
#define DEFAULT_QUADRATIC_ATTENUATION 0.2

#define DEFAULT_SPOT_CUTOFF 10.0
#define DEFAULT_SPOT_EXPONENT 0

LightStudio lightStudio;

//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
////          L I G H T :: L I G H T S E T T I N G S          ////
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////

Light::LightSettings::LightSettings(){
    disable();
    intensity=0;
    r=g=b=1.0;
}
Light::LightSettings::~LightSettings(){}
float					Light::LightSettings::getRed(){return r;}
float					Light::LightSettings::getGreen(){return g;}
float					Light::LightSettings::getBlue(){return b;}
float					Light::LightSettings::getIntensity(){return intensity;}
Light::LightSettings&	Light::LightSettings::setIntensity(float intensity){
    this->intensity=intensity;
    return *this;
}
Light::LightSettings&	Light::LightSettings::setColor(int hexColor){
    r = (float)(hexColor & 0xFF0000)/(float)0xFF0000;
    g = (float)(hexColor & 0x00FF00)/(float)0x00FF00;
    b = (float)(hexColor & 0x0000FF)/(float)0x0000FF;
    return *this;
}
Light::LightSettings&	Light::LightSettings::setColor(float r, float g, float b){
    this->r=r;
    this->g=g;
    this->b=b;
    return *this;
}
Light::LightSettings&	Light::LightSettings::disable(){
    use = false;
    return *this;
}
Light::LightSettings&	Light::LightSettings::enable(){
    use = true;
    return *this;
}
void					Light::LightSettings::drawMe(GLenum lightEnum,GLenum lightType){
	GLfloat color[4],cc[4];
	if (use)
	{
		// Set lighting intensity and color
		color[0] = color[1] = color[2] = intensity;
		color[3] = 1.0f;
		
		// Set material properties
		cc[0]=r;
		cc[1]=g;
		cc[2]=b;
	}
	else
	{
		// Set lighting intensity and color
		color[0]=color[1]=color[2]=color[3]=cc[0]=cc[1]=cc[2]=0.0f;
	}
	cc[3]=1.0f;
	glLightfv(lightEnum, lightType, color);
	glMaterialfv(GL_FRONT, lightType, cc);
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
////          L I G H T :: L I G H T S E T T I N G S S P E C U L A R         ////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

Light::LightSettingsSpecular::LightSettingsSpecular(){
	cutoff = DEFAULT_SPOT_CUTOFF;
	exponent = DEFAULT_SPOT_EXPONENT;
}
Light::LightSettingsSpecular::~LightSettingsSpecular(){}
double Light::LightSettingsSpecular::getCuffoff(){return cutoff;}
int Light::LightSettingsSpecular::getExponent(){return exponent;}
Light::LightSettingsSpecular& Light::LightSettingsSpecular::setCutoff(double cutoff){this->cutoff=cutoff;return *this;}
Light::LightSettingsSpecular& Light::LightSettingsSpecular::setExponent(int exponent){this->exponent=exponent;return *this;}
void Light::LightSettingsSpecular::drawSpec(GLenum lightEnum, GLenum lightType){
	glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, cutoff);
	glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, exponent);
	drawMe(lightEnum, lightType);
}

/////////////////////////////////////
/////////////////////////////////////
////          L I G H T          ////
/////////////////////////////////////
/////////////////////////////////////

Light::Light(){
    requestControl();
    ambient.setIntensity(DEFAULT_INTENSITY_AMBIENT);
    diffuse.setIntensity(DEFAULT_INTENSITY_DIFFUSE);
    specular.setIntensity(DEFAULT_INTENSITY_SPECULAR);
}
Light::~Light(){
    lightStudio.releaseLightControl(this);
}
bool	Light::requestControl(){
    lightNum = lightStudio.requestLightControl(this);
    return lightNum==-1?false:true;
}
void	Light::draw(){
    if (hasLightControl())
    {
        if (!gameController.isLightingEnabled()){return;}
		// Set the light position
		GLfloat pos[4],dir[4];
		GLenum lightEnum;
		
		lightEnum = getLightEnum();
		
		pos[0] = (float)position.getX();
		pos[1] = (float)position.getY();
		pos[2] = (float)position.getZ();
		pos[3] = 1.0f;
		glDisable(lightEnum);
		glEnable(lightEnum);
		glLightfv(lightEnum, GL_POSITION, pos);
		dir[0] = (float)direction.getI();
		dir[1] = (float)direction.getJ();
		dir[2] = (float)direction.getK();
		glLightfv(getLightEnum(), GL_SPOT_DIRECTION, dir);
		glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, attenuationConstant);
		glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, attenuationLinear);
		glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, attenuationQuadratic);
		
		ambient.drawMe(lightEnum,GL_AMBIENT);
		diffuse.drawMe(lightEnum,GL_DIFFUSE);
		specular.drawSpec(lightEnum,GL_SPECULAR);
		
    }
    else
    {
        requestControl();
    }
}
double	Light::getAttenuationConstant(){return attenuationConstant;}
double	Light::getAttenuationLinear(){return attenuationLinear;}
double	Light::getAttenuationQuadratic(){return attenuationQuadratic;}
Light&	Light::setAttenuationConstant(double value){attenuationConstant=value;return *this;}
Light&	Light::setAttenuationLinear(double value){attenuationLinear=value;return *this;}
Light&	Light::setAttenuationQuadratic(double value){attenuationQuadratic=value;return *this;}
bool	Light::hasLightControl(){
    return lightNum==-1?false:true;
}
GLenum	Light::getLightEnum(){
    switch (lightNum)
    {
    case 0:
        return GL_LIGHT0;
    case 1:
        return GL_LIGHT1;
    case 2:
        return GL_LIGHT2;
    case 3:
        return GL_LIGHT3;
    case 4:
        return GL_LIGHT4;
    case 5:
        return GL_LIGHT5;
    case 6:
        return GL_LIGHT6;
    case 7:
        return GL_LIGHT7;
    default:
        errLog.print("trying to use a non existant light in Light::getLightEnum");
        assert(!"trying to use a non existant light in Light::getLightEnum");
        return 0;
    }
}
GLenum	Light::getLightEnum(int num){
    switch (num)
    {
        case 0:
            return GL_LIGHT0;
        case 1:
            return GL_LIGHT1;
        case 2:
            return GL_LIGHT2;
        case 3:
            return GL_LIGHT3;
        case 4:
            return GL_LIGHT4;
        case 5:
            return GL_LIGHT5;
        case 6:
            return GL_LIGHT6;
        case 7:
            return GL_LIGHT7;
        default:
            errLog.print("trying to use a non existant light in Light::getLightEnum(int)");
            assert(!"trying to use a non existant light in Light::getLightEnum(int)");
            return 0;
    }
}

/////////////////////////////////////////////////
/////////////////////////////////////////////////
////          L I G H T S T U D I O          ////
/////////////////////////////////////////////////
/////////////////////////////////////////////////

LightStudio::LightStudio(){
    for (int i=0;i<8;i++)
    {inUse[i] = 0;}
}
LightStudio::~LightStudio(){
    //not sure
}
int				LightStudio::requestLightControl(Light *light){
    for (int i=0;i<8;i++)
    {
        if (inUse[i]==0)
        {
            inUse[i]=light;
            glEnable(Light::getLightEnum(i));
            mainLog(Str()<<"Enabling LIGHT"<<i<<" control to a new light");
            return i;
        }
    }
    return -1;//full
}
bool			LightStudio::releaseLightControl(Light *light){
    for (int i=0;i<8;i++)
    {
        if (inUse[i]==light)
        {
            inUse[i]=0;
            mainLog(Str()<<"Disabling LIGHT"<<i<<" for an object");
            glDisable(Light::getLightEnum(i));
            return true;
        }
    }
    return false;
}
LightStudio&	LightStudio::setGlobalAmbientLight(int hexColor){
    ambientLight = hexColor;
    float cc[4];
    cc[0]=(hexColor&0xFF0000)/(float)0xFF0000;
    cc[1]=(hexColor&0x00FF00)/(float)0x00FF00;
    cc[2]=(hexColor&0x0000FF)/(float)0x0000FF;
    cc[3]=1.0f;
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, cc);
    return *this;
}
int				LightStudio::getGlobalAmbientLight(){
    return ambientLight;
}
